/**
 * 
 */
package top.ieei.demo.ftp.client;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.SocketAddress;

import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPReply;
import org.apache.log4j.Logger;



/**
 * <p>
 * FTP客户端程序
 * </p>
 * @author xiecongshu
 * @since：2018年10月10日 下午5:00:28
 */
public class FtpClient
{
	/**
	 * 日志记录
	 */
	private static Logger log = Logger.getLogger(FtpClient.class);
	
	/**
	 * 文件系统目录分隔符
	 */
	public static final String SEPARATOR = System.getProperty("file.separator");
	
	/** 本地字符编码 */
	private static String localCharset = "GBK";

	/**
	 * FTP协议里面，规定文件名编码为iso-8859-1
	 */
	private static String serverCharset = "ISO-8859-1";

	/**
	 * 服务主机地址
	 */
	private String host = "127.0.0.1";

	/**
	 * 服务主机端口
	 */
	private int port = 21;

	/**
	 * FTP登录用户
	 */
	private String ftpUser = "anonymous";

	/**
	 * 是否使用Apache Ftp客户端
	 */
	private boolean useApacheFtp = true;

	/**
	 * FTP登录密码
	 */
	private String ftpPassword = "";

	/**
	 * apacheClient
	 */
	private FTPClient apacheClient = null;
	
	/**
	 * sunClient
	 */
	private sun.net.ftp.FtpClient sunClient = null;
	
	/**
	 * 是否被动模式<br>
	 * (主动模式和被动模式的不同简单概述为： 主动模式传送数据时是“服务器”连接到“客户端”的端口；被动模式传送数据是“客户端”连接到“服务器”的端口)
	 */
	private boolean isPassiveMode = true;
	
	/**
	 * 是否已经连接FTP服务
	 */
	private boolean isConnected = false;
	
	

	/**
	 * 构造函数
	 * @param host
	 * @param port
	 * @param ftpUser
	 * @param password
	 */
	public FtpClient(String host, int port, String ftpUser, String password) {
		this.host = host;
		this.port = port;
		this.ftpUser = ftpUser;
		this.ftpPassword = password;
		log.info("默认使用Apache FTP客户端。");
		apacheClient = new FTPClient();
	}
	
	public void cd(String remoteDir){
		connect();
		try
		{
			log.debug("执行FTP服务端目录切换，目标工作目录:" + formatToLinuxPath(remoteDir));
			if(useApacheFtp)
			{
				apacheClient.changeWorkingDirectory(remoteDir);
			}else
			{
				sunClient.changeDirectory(remoteDir);
			}
		} catch (Exception e)
		{
			log.error("访问FTP服务异常:" + clientInfo(e.getMessage()), e);
			disconnect();
			throw new RuntimeException(e);
		}
	}
	
	/**
	 * 下载文件
	 * <br>
	 * @param remoteFile
	 * @param localDir
	 * @param newFileName
	 * @return
	 * @author xiecongshu
	 */
	public String downloadFile(String remoteFile, String localDir, String newFileName)
	{
		String localFile = formatPath(localDir);
		remoteFile = formatToLinuxPath(remoteFile);
		File path = new File(remoteFile);
		String remoteDir = path.getParent();
		String remoteFileName = path.getName();
		localFile = localFile + (newFileName == null ? remoteFileName : newFileName);
		cd(remoteDir);
		InputStream is = null;
		OutputStream os = null;
		try
		{
			// 对中文文件名进行转码，否则中文名称的文件下载失败
			remoteFileName = new String(remoteFileName.getBytes(localCharset), serverCharset);
			is = apacheClient.retrieveFileStream(remoteFileName);
			os = new FileOutputStream(localFile);
			byte[] buff = new byte[4096];
			int length = 0;
			while ((length = is.read(buff)) != -1)
			{
				os.write(buff, 0, length);
			}
			os.flush();
			log.info("已经下载文件:" + getWorkingDir() + "/" + path.getName());
		} catch (Exception e)
		{
			log.error("FTP下载文件异常:" + clientInfo(e.getMessage()), e);
			throw new RuntimeException("FTP下载文件异常");
		} finally
		{
			if (is != null)
			{
				try
				{
					is.close();
				} catch (IOException e)
				{
					log.error("关闭FTP下载文件流异常:" + clientInfo(e.getMessage()), e);
				}
			}
			if (os != null)
			{
				try
				{
					os.close();
				} catch (IOException e)
				{
					log.error("本地写入流关闭异常:" + clientInfo(e.getMessage()), e);
				}
			}
		}
		return localFile;
	}
	
	/**
	 * 上传文件
	 * <br>
	 * @param localFile 本地文件
	 * @param remoteDir 远程目录
	 * @param remoteFileName 远程文件名
	 * @return
	 * @author xiecongshu
	 */
	public boolean uploadFile(String localFile, String remoteDir, String remoteFileName)
	{
		remoteDir = formatToLinuxPath(remoteDir);
		cd(remoteDir);
		InputStream is = null;
		try
		{
			is = new FileInputStream(localFile);
			return apacheClient.storeFile(remoteFileName, is);
		} catch (Exception e)
		{
			log.error("FTP上传文件异常:" + clientInfo(e.getMessage()), e);
			throw new RuntimeException("FTP上传文件异常");
		} finally
		{
			if (is != null)
			{
				try
				{
					is.close();
				} catch (IOException e)
				{
					log.error("关闭FTP下载文件流异常:" + clientInfo(e.getMessage()), e);
				}
			}
		}
	}
	
	/**
	 * 上传文件
	 * <br>
	 * @param fileData 文件内容
	 * @param remoteDir 远程目录
	 * @param remoteFileName 远程文件名
	 * @return
	 * @author xiecongshu
	 */
	public boolean uploadFile(byte[] fileData, String remoteDir, String remoteFileName)
	{
		remoteDir = formatToLinuxPath(remoteDir);
		cd(remoteDir);
		InputStream is = null;
		try
		{
			is = new ByteArrayInputStream(fileData);
			return apacheClient.storeFile(remoteFileName, is);
		} catch (Exception e)
		{
			log.error("FTP上传文件异常:" + clientInfo(e.getMessage()), e);
			throw new RuntimeException("FTP上传文件异常");
		} finally
		{
			if (is != null)
			{
				try
				{
					is.close();
				} catch (IOException e)
				{
					log.error("关闭FTP下载文件流异常:" + clientInfo(e.getMessage()), e);
				}
			}
		}
	}
	
	/**
	 * 格式化文件目录路径
	 * @param path
	 * @return 路径结尾包含目录分隔符
	 * @author xiecongshu
	 * @since：2018年6月7日 下午3:08:22
	 */
	private String formatToLinuxPath(String path)
	{
		File dir = new File(path);
		path = dir.getPath();
		return path.replaceAll("\\\\", "/");
	}
	
	/**
	 * 格式化文件目录路径
	 * @param path
	 * @return 路径结尾包含目录分隔符
	 * @author xiecongshu
	 * @since：2018年6月7日 下午3:08:22
	 */
	private String formatPath(String path)
	{
		File dir = new File(path);
		path = dir.getPath();
		if (!path.endsWith(SEPARATOR))
		{
			path += SEPARATOR;
		}
		return path;
	}
	
	/**
	 * 断开FTP服务<br>
	 * @author xiecongshu
	 * @since：2018年10月10日 下午6:45:54
	 */
	public void disconnect(){
		try
		{
			isConnected = false;
			if(useApacheFtp && apacheClient != null && apacheClient.isConnected())
			{
				apacheClient.logout();
				apacheClient.disconnect();
				log.info("已经断开FTP服务");
			}else if(sunClient != null && sunClient.isConnected())
			{
				sunClient.close();
				log.info("已经断开FTP服务");
			}
		} catch (IOException e)
		{
			log.error("断开FTP服务异常:" + clientInfo(e.getMessage()), e);
		}
	}

	/**
	 * 切换到用户根目录<br>
	 * @author xiecongshu
	 * @since：2018年10月10日 下午5:43:29
	 */
	public void cdToHome(){
		cd("/");
	}
	
	/**
	 * 获取当前工作目录<br>
	 * @return
	 * @author xiecongshu
	 * @since：2018年10月10日 下午5:55:07
	 */
	public String getWorkingDir(){
		connect();
		try
		{
			if(useApacheFtp)
			{
				return apacheClient.printWorkingDirectory();
			}else
			{
				return sunClient.getWorkingDirectory();
			}
		} catch (Exception e)
		{
			log.error("访问FTP服务异常:" + clientInfo(e.getMessage()), e);
			disconnect();
			throw new RuntimeException(e);
		}
	}

	/**
	 * 设置否使用ApacheFtp客户端的值
	 * @param useApacheFtp 是否使用ApacheFtp客户端（fasle:使用Sun Ftp客户端）
	 */
	public void setUseApacheFtp(boolean useApacheFtp)
	{
		if(!(this.useApacheFtp ^ useApacheFtp)){
			return;
		}
		if(isConnected){
			disconnect();
		}
		if(useApacheFtp){
			apacheClient = new FTPClient();
			sunClient = null;
			log.info("设置使用Apache FTP客户端。");
		}else{
			sunClient = sun.net.ftp.FtpClient.create();
			apacheClient = null;
			log.info("设置使用Sun FTP客户端。");
		}
		this.useApacheFtp = useApacheFtp;
	}



	/**
	 * 连接FTP服务器<br>
	 * @return
	 * @author xiecongshu
	 * @since：2018年10月10日 下午5:36:51
	 */
	private void connect()
	{
		if(isConnected){
			return;
		}
		if (useApacheFtp)
		{
			try
			{
				if(apacheClient == null){
					apacheClient = new FTPClient();
				}
				apacheClient.connect(host, port);
				// 连接FTP服务器
				apacheClient.login(ftpUser, ftpPassword);
				if (isPassiveMode)
				{
					apacheClient.enterLocalPassiveMode(); 
				}else
				{
					apacheClient.enterLocalActiveMode();
				}
				// 登陆FTP服务器
				if (!FTPReply.isPositiveCompletion(apacheClient.getReplyCode()))
				{
					log.info("未连接到FTP，用户名或密码错误。");
					apacheClient.disconnect();
				} else
				{
					log.info("FTP连接成功。");
					isConnected = true;
				}
				// 设置上传文件的类型为二进制类型
				apacheClient.setFileType(FTP.BINARY_FILE_TYPE);
				// 开启服务器对UTF-8的支持，如果服务器支持就用UTF-8编码，否则就使用本地编码（GBK）
				if (FTPReply.isPositiveCompletion(apacheClient.sendCommand("OPTS UTF8", "ON"))) {
					localCharset = "UTF-8";
				}
				apacheClient.setControlEncoding(localCharset);
			} catch (Exception e)
			{
				log.error("连接FTP服务异常:" + clientInfo(e.getMessage()), e);
				disconnect();
				throw new RuntimeException(e);
			}
		}else
		{
			try
			{
				SocketAddress addr = new InetSocketAddress(host, port);
				if(sunClient == null){
					sunClient = sun.net.ftp.FtpClient.create();
				}
				sunClient.connect(addr);
				sunClient.login(ftpUser, ftpPassword.toCharArray());
				sunClient.setBinaryType();
				isConnected = true;
			} catch (Exception e)
			{
				log.error("连接FTP服务异常:" + clientInfo(e.getMessage()), e);
				disconnect();
				throw new RuntimeException(e);
			}
		}
	}

	/**
	 * FTP连接信息<br>
	 * @param msg
	 * @return
	 * @author xiecongshu
	 * @since：2018年10月10日 下午5:35:56
	 */
	private String clientInfo(String msg)
	{
		StringBuilder sb = new StringBuilder();
		sb.append("FTP连接信息[FTP服务主机=");
		sb.append(host).append(",服务端口=");
		sb.append(port).append(",用户名=");
		sb.append(ftpUser).append(",密码=");
		sb.append(ftpPassword).append("]");
		sb.append(msg);
		return sb.toString();
	}

	/**
	 * 获取是否被动模式的值
	 * @return 是否被动模式
	 */
	public boolean isPassiveMode()
	{
		return isPassiveMode;
	}


	/**
	 * 设置是否被动模式的值
	 * @param isPassiveMode 是否被动模式
	 */
	public void setPassiveMode(boolean isPassiveMode)
	{
		this.isPassiveMode = isPassiveMode;
	}
	
	public static void main(String[] args)
	{
		FtpClient ftp = new FtpClient("127.0.0.1", 21, "ftpuser", "ftpuser");
		ftp.setUseApacheFtp(false);
		//ftp.cd("/1/2");
		System.out.println(ftp.getWorkingDir());
		ftp.cdToHome();
		System.out.println(ftp.getWorkingDir());
		//中文文件
		String remoteFile = "/1/2/中文文件.txt"; String localDir = "D:\\opt\\";String newFileName = "信息d.txt";
		System.out.println(ftp.downloadFile(remoteFile, localDir, newFileName));
		ftp.disconnect();
	}
}
